package game.connect4;

import java.io.Serializable;
import java.util.Arrays;

import exceptions.InvalidMoveException;
import game.Player;


/**
 * Board class
 * Provides methods to manage the game board.
 */
public class Board implements Serializable {
	private static final long serialVersionUID = -3966048823077258994L;
	public static final int WIDTH  = 7; // Board width
	public static final int HEIGHT = 6; // Board height
	private static final Player nullPlayer = new Player("", "", "");
	
	public static enum State { Running, Finished, Tied }
	
	
	/**
	 * Constructor.
	 * 
	 * Initializes every board slot to nullPlayer.
	 */
	public Board() {
		this.boardArray = new Player[HEIGHT][WIDTH];
		
		for (int i = 0; i < HEIGHT; i++)
			Arrays.fill(this.boardArray[i], nullPlayer);
	}
	
	
	/**
	 * Inserts a new piece at the given column
	 * 
	 * @param player Player who performed the move
	 * @param column Place where the piece is being inserted on.
	 * @return Running if the game is not finished, Tied if the game ended in a tie, Finished if the game finished and not tied.
	 */
	public State play(Player player, int column) throws InvalidMoveException {
		if (isColumnFilled(column)) {		
			throw new RuntimeException("Invalid move");
		}
		
		int row = insertAt(column, player);
		
		if (checkForCompletionAt(row, column))
			return State.Finished;
		
		if (this.countMoves() == HEIGHT * WIDTH)
			return State.Tied;
		
		return State.Running;
	}
	
	
	/**
	 * Calculates the number of moves that have already been performed.
	 * 
	 * @return The number of moves performed.
	 */
	public int countMoves() {
		int moves = 0;
		
		for (int i = 0; i < HEIGHT; i++)
			for (int j = 0; j < WIDTH; j++)
				if (!this.boardArray[i][j].equals(nullPlayer))
					moves++;
					
		return moves;			
	}
	
	
	/**
	 * Is the column filled?
	 * 
	 * @param column The column index to check.
	 * @return True if the column is filled, false otherwise.
	 */
	public boolean isColumnFilled(int column) {
		return !this.boardArray[0][column].equals(nullPlayer);
	}
	
	
	/**
	 * Calculates the number of moves made by player.
	 * 
	 * @param player The player who made the moves.
	 * @return The number of moves performed.
	 */
	public int countMovesForPlayer(Player player) {
		int moves = 0;
		
		for (int i = 0; i < HEIGHT; i++)
			for (int j = 0; j < WIDTH; j++)
				if (this.boardArray[i][j].equals(player))
					moves++;
					
		return moves;
	}
	
	
	/**
	 * Inserts a new Piece at a specified column.
	 * 
	 * @param column The column where the piece will be inserted.
	 * @param player The Player who made this move.
	 * @return The row-index at the specified column where the piece was inserted. 
	 *         -1 if the column is already filled.
	 */
	private int insertAt(int column, Player player) {
		int row;
		
		for (row = -1; row < HEIGHT - 1 && this.boardArray[row + 1][column].equals(nullPlayer); row++);
		
		if (row != -1)
			this.boardArray[row][column] = player;
		
		return row;
	}
	
		
	/**
	 * Checks for 4 consecutive pieces in all directions, starting at the
	 * specified position.
	 * 
	 * @param row The row where to begin searching.
	 * @param column The column where to begin searching.
	 * @return True if four pieces were found. False otherwise.
	 */
	private boolean checkForCompletionAt(int row, int column) {
		return checkForCompletionHelper(row, column, 1, 0) ||
			checkForCompletionHelper(row, column, 0, 1) ||
			checkForCompletionHelper(row, column, 1, 1) ||
			checkForCompletionHelper(row, column, 1, -1);
	}
	
	
	/**
	 * Checks for 4 pieces from the same player, starting at (row, column) on direction
	 * given by the vector (horizontal, vertical)
	 * 
	 * @param row The row where the search begins.
	 * @param column The column where the search begins.
	 * @param vertical The vertical coordinate of the direction to follow.
	 * @param horizontal The horizontal coordinate of the direction to follow.
	 * @return True if 4 consecutive pieces were found. False otherwise.
	 */	
	private boolean checkForCompletionHelper(int row, int column, int vertical, int horizontal) {
		int pieces = 1;  // Since there is a piece at (row, column)
		Player player = this.boardArray[row][column];
		
		// Searches forwards
		for (int i = row - vertical, j = column - horizontal; i >= 0 && j >= 0 && i < HEIGHT && j < WIDTH; i -= vertical, j -= horizontal) {
			if (this.boardArray[i][j].equals(player))
				pieces++;
			else
				break;
		}
				
		// Searches backwards
		for (int i = row + vertical, j = column + horizontal; i >= 0 && j >= 0 && i < HEIGHT && j < WIDTH; i += vertical, j += horizontal) {
			if (this.boardArray[i][j].equals(player))
				pieces++;
			else
				break;
		}
		
		return pieces >= 4;
	}
	
	/*
	 * Each slot of the board contains the player who put a piece there.
	 * All slots are initialized to nullPlayer in the constructor.
	 * 
	 * The boardArray is organized as follows:
	 * 
	 *   <-- board top -->
	 *   0 1 2 3 4 5 6
	 * 0  	
	 * 1
	 * 2   x
	 * 3   o   x
	 * 4   o o x
	 * 5 x o x o o x
	 *  <-- board bottom ->
	 */
	private Player[][] boardArray;
}
